/*
 * Copyright (c) 2025 Industrial Software Feature Database
 */
package com.comac.ins.common.biz.aspectj;

import java.lang.reflect.Method;
import java.util.Arrays;
import java.util.HashMap;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;

import org.apache.commons.lang3.StringUtils;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Pointcut;
import org.aspectj.lang.reflect.CodeSignature;
import org.aspectj.lang.reflect.MethodSignature;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.slf4j.MDC;
import org.springframework.context.expression.MapAccessor;
import org.springframework.core.annotation.Order;
import org.springframework.expression.Expression;
import org.springframework.expression.spel.standard.SpelExpressionParser;
import org.springframework.expression.spel.support.StandardEvaluationContext;
import org.springframework.stereotype.Component;

import com.comac.ins.common.core.domain.R;
import com.comac.ins.common.core.domain.model.LoginUser;
import com.comac.ins.common.biz.annotations.Digest;
import com.comac.ins.common.satoken.utils.LoginHelper;

import static com.comac.ins.common.core.constant.Constants.TRACE_ID;

/**
 * 摘要日志切面拦截器
 *
 * @author zhanghongyi
 * @date 2024/6/20 13:36
 * @description DigestAspect
 */
@Component
@Aspect
@Order(10)
public class DigestAspect {
    private static final Logger logger = LoggerFactory.getLogger(DigestAspect.class);
    private static final Logger diggestLogger = LoggerFactory.getLogger("digest_logger");
    private static final SpelExpressionParser PARSER = new SpelExpressionParser();
    private static final Map<String, Expression> PARSED_EXPRESSION_CACHE = new ConcurrentHashMap<>();
    private static final String RESULT_KEY = "RET";
    private static final String YES = "Y";
    private static final String NO = "N";
    private static final String SPLIT = "|";

    /**
     * 对加了注解的方法进行拦截
     */
    @Pointcut("@annotation(com.comac.ins.common.biz.annotations.Digest)")
    public void pointCut() {
    }

    /**
     * 摘要日志切面
     */
    @Around("pointCut()")
    public Object around(ProceedingJoinPoint proceedingJoinPoint) throws Throwable {

        Map<String, Object> paramMap = getRequestParamMap(proceedingJoinPoint);
        long beginTime = System.currentTimeMillis();
        boolean success = true;
        Object result = null;
        String msg = "";
        try {
            result = proceedingJoinPoint.proceed();
            success = getSuccess(result);
            msg = getMsg(result);
            setTraceId(result);
        } catch (Throwable throwable) {
            //调用发生异常则标记失败并抛出
            success = false;
            msg = throwable.getMessage();
            throw throwable;
        } finally {
            //日志拦截不应影响主逻辑，故加一个大的try/catch
            try {
                paramMap.put(RESULT_KEY, result);
                long latency = System.currentTimeMillis() - beginTime;

                MethodSignature methodSign = (MethodSignature) proceedingJoinPoint.getSignature();
                Method method = methodSign.getMethod();
                Digest digestAnnotation = method.getAnnotation(Digest.class);
                //没有填写bizType则以方法名为bizType
                String bizType = digestAnnotation.bizType();
                if (StringUtils.isBlank(bizType)) {
                    bizType = methodSign.getName();
                }
                diggestLogger.info(buildDigest(bizType, success, msg, latency, paramMap, digestAnnotation.logFields()));
            } catch (Throwable throwable) {
                logger.warn("digest occur exception", throwable);
            }
        }

        return result;
    }

    /**
     * 构建摘要日志
     *
     * @param bizType                业务类型标识
     * @param success                是否成功
     * @param latency                处理延迟时间
     * @param paramMap               参数Map
     * @param customFieldExpressions 自定义字段表达式（SPel表达式）
     */
    private String buildDigest(String bizType, boolean success,String msg, long latency, Map<String, Object> paramMap, String[] customFieldExpressions) {
        StandardEvaluationContext context = new StandardEvaluationContext(paramMap);
        context.addPropertyAccessor(new MapAccessor());
        StringBuilder sb = new StringBuilder();
        sb.append(bizType);
        sb.append(SPLIT);
        sb.append(success ? YES : NO);
        sb.append(SPLIT);
        sb.append(latency);
        sb.append("ms");
        sb.append(SPLIT);
        Long uid = getLoginUserId();
        sb.append(null == uid ? "" : uid);
        sb.append(SPLIT);
        sb.append(msg);
        for (String fieldExpression : customFieldExpressions) {
            String field = "";
            try {
                Expression expression = PARSED_EXPRESSION_CACHE.computeIfAbsent(fieldExpression, key -> PARSER.parseExpression(fieldExpression));
                field = expression.getValue(context, String.class);
            } catch (Throwable throwable) {
                logger.error("calc expression fail", throwable);
            }
            sb.append(SPLIT);
            sb.append(null == field ? "" : field);
        }
        return sb.toString();
    }

    /**
     * 获取参数Map集合
     *
     * @param joinPoint 连接点
     * @return 参数Map
     */
    private Map<String, Object> getRequestParamMap(ProceedingJoinPoint joinPoint) {
        Map<String, Object> param = new HashMap<>(10);
        Object[] paramValues = joinPoint.getArgs();
        String[] paramNames = ((CodeSignature) joinPoint.getSignature()).getParameterNames();
        for (int i = 0; i < paramNames.length; i++) {
            param.put(paramNames[i], paramValues[i]);
        }
        return param;
    }

    /**
     * 获取登录用户Id
     */
    private Long getLoginUserId() {
        try {
            LoginUser loginUser = LoginHelper.getLoginUser();
            return null != loginUser ? loginUser.getUserId() : null;
        }catch (Exception e){
            logger.warn("get loginUserId fail {}", e.getMessage());
            return null;
        }
    }

    private Boolean getSuccess(Object result) {
        if (result instanceof R<?> rsp) {
            return R.isSuccess(rsp);
        }
        if(null == result){
            return true;
        }
        //通过反射常识性获取调用结果
        for (Method method : result.getClass().getMethods()) {
            try {
                if ("isSuccess".equals(method.getName())) {
                    return (Boolean) method.invoke(result);
                }
                if ("getCode".equals(method.getName())) {
                    Object code =  method.invoke(result);
                    if(code instanceof String codeString){
                        return Arrays.asList("200","0").contains(codeString);
                    }
                    if(code instanceof Integer codeInt){
                        return Arrays.asList(200,0).contains(codeInt);
                    }
                }
            } catch (Exception e) {
                logger.debug("invoke fail:{}", e.getMessage());
            }
        }
        return true;
    }
    private String getMsg(Object result) {
        if(null == result){
            return "";
        }
        //通过反射常识性获取信息
        for (Method method : result.getClass().getMethods()) {
            try {
                if ("getMsg".equals(method.getName())) {
                    Object code =  method.invoke(result);
                    if(code instanceof String codeString){
                        return codeString;
                    }
                }
            } catch (Exception e) {
                logger.debug("invoke fail:{}", e.getMessage());
            }
        }
        return "";
    }

    private void setTraceId(Object result) {
        if (result instanceof R<?> rsp) {
            rsp.setTraceId(MDC.get(TRACE_ID));
        }
    }
}
